package paperfly.input;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

public class FlyPanel extends JPanel implements Runnable {
	private static final long serialVersionUID = 4431358176335341996L;
	private static final int PWIDTH = 300; // size of panel
	private static final int PHEIGHT = 250;

	private static final int NO_DELAYS_PER_YIELD = 16;
	private Thread animator; 
	private volatile boolean running = false; 
	private volatile boolean isPaused = false;
	private long period; 

	private Graphics2D dbg2;
	private Image dbImage = null;

	private ImageAnalyzer imAnalyzer; 
	private BufferedImage aviao = null; 
	private BufferedImage fundo = null;
	private BufferedImage imagemCamera = null; 
	private Point asaDireita = null; 
	private double anguloAsas = 0; 
	private boolean mostrarImagemCamera;

	public FlyPanel(long period) {
		this.period = period;

		setDoubleBuffered(false);
		setBackground(Color.black);
		Dimension d = new Dimension(PWIDTH, PHEIGHT);
		setMinimumSize(d);
		setPreferredSize(d);

		setFocusable(true);
		requestFocus(); 

		addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				processKey(e);
			}
		});

		ImageLoader imagesLoader = new ImageLoader();
		aviao = imagesLoader.loadImage("images/input-calibration/redBaron.gif", 205, 87);
		fundo = imagesLoader.loadImage("images/input-calibration/sky.jpg", PWIDTH, PHEIGHT);
		imAnalyzer = new ImageAnalyzer(this);
		imAnalyzer.start();
	}

	private void processKey(KeyEvent e) {
		int keyCode = e.getKeyCode();

		if ((keyCode == KeyEvent.VK_ESCAPE) || (keyCode == KeyEvent.VK_Q)
				|| (keyCode == KeyEvent.VK_END)
				|| ((keyCode == KeyEvent.VK_C) && e.isControlDown()))
			running = false;
	}

	public void addNotify() {
		super.addNotify(); 
		if (animator == null || !running) {
			animator = new Thread(this);
			animator.start();
		}
	}

	public void resumeGame() {
		isPaused = false;
	}

	public void pauseGame() {
		isPaused = true;
	}

	public void stopGame() {
		running = false;
	}

	public void run() {
		long inicio, depois, diferenca, sleepTime;
		long overSleepTime = 0L;
		int noDelays = 0;
		long excess = 0L;

		inicio = System.nanoTime(); 
		running = true;

		while (running) {
			gameRender();
			paintScreen();

			depois = System.nanoTime();
			diferenca = depois - inicio;
			sleepTime = (period - diferenca) - overSleepTime;

			if (sleepTime > 0) { 
				try {
					Thread.sleep(sleepTime / 1000000L); 
				} catch (InterruptedException ex) {}
				overSleepTime = (System.nanoTime() - depois) - sleepTime;
			} else { 
				excess -= sleepTime; 
				overSleepTime = 0L;

				if (++noDelays >= NO_DELAYS_PER_YIELD) {
					Thread.yield(); 
					noDelays = 0;
				}
			}

			inicio = System.nanoTime();

		}

		imAnalyzer.closeDown();
		System.exit(0); 
	}

	/*
	 * Called from ImageAnalyzer with the modified snap image, the coordinates
	 * of the wand head, and the wand's angle to the horizontal. Since
	 * ImageAnalyzer is running in its own thread, this method may be called at
	 * any time.
	 */
	public void updateCamInfo(BufferedImage im, Point hdPt, double angle) {
		if (!isPaused) {
			imagemCamera = im;
			asaDireita = hdPt;
			anguloAsas = angle;
		}
	}

	/*
	 * The game scene is composed from a background image, the webcam image, a
	 * picture of a wand, and blast sprites shot from the wand.
	 */
	private void gameRender() {
		if (dbImage == null) {
			dbImage = createImage(PWIDTH, PHEIGHT);
			if (dbImage == null) {
				System.out.println("dbImage is null");
				return;
			} else
				dbg2 = (Graphics2D) dbImage.getGraphics();
		}

		dbg2.drawImage(fundo, 0, 0, this);
		if (imagemCamera != null) { 
			if (isMostrarImagemCamera()) {
				dbg2.drawImage(imagemCamera, 0, 0, this);
			}

			if (asaDireita != null) {
				int xHead = asaDireita.x;
				int yHead = asaDireita.y;
				double angulo = anguloAsas; 

				desenharAviao(dbg2, xHead, yHead, angulo);
			}
		}
	}

	/*
	 * Draw the wand image after translating the drawing coordinates system to
	 * the wand's head position, and rotating them by angle. Use AffineTransform
	 * to modify the coordinates system.
	 */
	private void desenharAviao(Graphics2D g2, int xHead, int yHead, double angle) {
		AffineTransform oldTrans = g2.getTransform(); 

		AffineTransform posHead = new AffineTransform();
		posHead.translate(xHead - 3, yHead - 22);
		
		posHead.rotate(angle);
		g2.transform(posHead); 
		int posicaoX = -aviao.getWidth() / 4;
		int posicaoY = -aviao.getHeight() / 2;
		g2.drawImage(aviao, posicaoX, posicaoY, this);
		g2.setTransform(oldTrans);
	} 

	// use active rendering to put the buffered image on-screen
	private void paintScreen() {
		Graphics g;
		try {
			g = this.getGraphics();
			if ((g != null) && (dbImage != null))
				g.drawImage(dbImage, 0, 0, null);
			Toolkit.getDefaultToolkit().sync();

			g.dispose();
		} catch (Exception e) {
			System.out.println("Graphics context error: " + e);
		}
	}

	public BufferedImage getCamImage() {
		return imAnalyzer.captureImage();
	}

	public boolean isMostrarImagemCamera() {
		return mostrarImagemCamera;
	}

	public void setMostrarImagemCamera(boolean mostrarImagemCamera) {
		this.mostrarImagemCamera = mostrarImagemCamera;
	}

}
